// This file is distributed under a BSD license. See LICENSE.txt for details.

#include "_types.hpp"
#include "_startconsole.hpp"
#include "shadercompile.hpp"
#include <ctype.h>

// return filename with replaced extension (should include '.')
static const sChar *ReplaceExtension(const sChar *name,const sChar *newExt)
{
  static sChar buffer[512];
  
  sVERIFY(newExt[0] == '.');
  sCopyString(buffer,name,sizeof(buffer));

  // try and find last . in filename
  for(sInt i=sGetStringLen(buffer)-1;i>=0;i--)
    if(buffer[i] == '.')
      break;

  // append extension
  if(i == -1) // file does not currently have an extension
    sAppendString(buffer,newExt,sizeof(buffer));
  else // replace existing extension
    sCopyString(buffer+i,newExt,sizeof(buffer)-i);

  return buffer;
}

// make a c-compatible identifier
static const sChar *MakeCIdentifier(const sChar *name)
{
  static sChar buffer[512];

  sCopyString(buffer,"g_",sizeof(buffer));
  sAppendString(buffer,name,sizeof(buffer));
  for(sInt i=0;buffer[i];i++)
    if(!isalnum(buffer[i]))
      buffer[i] = '_';

  return buffer;
}

// save compiled shader as binary
static void SaveBinary(const sChar *out,const sChar *in,sShaderCompiler &compiler)
{
  // if no output filename specified, generate from input filename
  if(!out)
  {
    sU32 versionToken = compiler.GetShader()[0];
    if((versionToken & 0xffff0000) == 0xfffe0000) // vertex shader
      out = ReplaceExtension(in,".vsfr");
    else // we presume it's a pixel shader
      out = ReplaceExtension(in,".psfr");
  }

  // actually save the shader
  sSystem->SaveFile(out,(sU8 *) compiler.GetShader(),compiler.GetShaderSize()*4);
}

// save compiled shader as (c/c++) header
static void SaveHeader(const sChar *out,const sChar *in,sShaderCompiler &compiler)
{
  // if no output filename specified, generate from input filename
  if(!out)
    out = ReplaceExtension(in,".h");

  // generate the header file
  sText *text = new sText;
  text->PrintF("// this file is automatically generated from '%s', do not edit!\n\n",in);
  text->PrintF("static sU32 %s[] = {\n",MakeCIdentifier(in));

  const sU32 *data = compiler.GetShader();
  sInt count = compiler.GetShaderSize();

  for(sInt base=0;base<count;base+=7)
  {
    text->Print("  ");
    for(sInt i=0;i<7 && base+i<count;i++)
      text->PrintF("0x%08x,",data[base+i]);

    text->Print("\n");
  }

  text->Print("};");

  // save it
  sSystem->SaveFile(out,(sU8 *) text->Text,sGetStringLen(text->Text));
  delete text;
}

sInt sAppMain(sInt argc,sChar **argv)
{
  // command line parsing
  sChar *fileName = 0;
  sChar *outFileName = 0;
  sInt outMode = 0;

  for(sInt i=1;i<argc;i++)
  {
    if(!sCmpString(argv[i],"--out-binary"))
    {
      outMode = 0;
      if(i+1<argc && sCmpMem(argv[i+1],"--",2))
        outFileName = argv[++i];
    }
    else if(!sCmpString(argv[i],"--out-header"))
    {
      outMode = 1;
      if(i+1<argc && sCmpMem(argv[i+1],"--",2))
        outFileName = argv[++i];
    }
    else if(!sCmpMem(argv[i],"--",2))
    {
      sSystem->PrintF("Unknown command line option '%s'!\n",argv[i]);
      return 1;
    }
    else
      fileName = argv[i];
  }

  // processing
  if(!fileName)
  {
    sSystem->PrintF(
      "farbrausch Shader Compiler (c) Fabian \"ryg\" Giesen 2005\n\n"
      "Syntax: shadercompiler <options> source.vsh/psh\n\n"
      "Options:\n"
      "--out-binary [filename]  Output binary shader file (the default)\n"
      "--out-header [filename]  Output C/C++ header file\n");

    return 1;
  }
  else
  {
    // load source
    sChar *src = sSystem->LoadText(fileName);
    if(!src)
    {
      sSystem->PrintF("Couldn't open source file '%s'!\n",fileName);
      return 1;
    }

    // try to compile it
    sShaderCompiler compiler;
    sText *errors = new sText;

    if(!compiler.Compile(src,fileName,errors))
    {
      // we had compile errors, print them and exit
      sSystem->PrintF("%s",errors->Text);
      delete errors;
      return 1;
    }
    else
      sSystem->PrintF("%s\n",fileName);

    // compile successful, generate output
    switch(outMode)
    {
    case 0:
      SaveBinary(outFileName,fileName,compiler);
      break;

    case 1:
      SaveHeader(outFileName,fileName,compiler);
      break;
    }

    delete errors;
    delete[] src;
  }

  return 0;
}
